/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simple.utils.randoms;

import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DynamicMessage;

import java.util.List;
import java.util.Random;

/**
 * @author Tomas Rehak
 */
public class Randoms {

    private Random random = null;
    private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
    private static final int BITS_PER_HALF_BYTE = Byte.SIZE / 2;
    private static final int HEX_DIGITS_PER_LONG = Long.SIZE / BITS_PER_HALF_BYTE;
    private static final int LAST_HEX_DIGIT_MASK = 0x0f;

    public Randoms() {
        random = new Random();
    }

    public Randoms(long seed) {
        random = new Random(seed);
    }

    public Random getRandom() {
        return random;
    }

    /**
     * Generates random pseudo-valid (random TLD) hostname in form xxx.yyyy..../xxx/yyy <br/>
     *
     * @param minSubDomains   minimal number of subdomains to generate (must be >= 2)
     * @param maxSubDomains   maximum number of subdomains to generate
     * @param minPath         minimum number of path backslashes
     * @param maxPath         maximum number of path backslashes
     * @param minStringLength minimum length of string (must be >= 1)
     * @param maxStringLength maximum length of string
     * @return
     */
    public String randomHostName(int minSubDomains, int maxSubDomains, int minPath, int maxPath, int minStringLength, int maxStringLength) {
        Preconditions.checkArgument(minSubDomains >= 2, "minSubDomains < 2");
        Preconditions.checkArgument(minStringLength >= 1, "minStringLength < 1");
        char[] chars = "qwertyuiopasdfghjklzxcvbnm1234567890".toCharArray();
        StringBuilder b = new StringBuilder();
        // subdomains
        for (int i = minSubDomains; i < maxSubDomains; i++) {
            String subD = randomString(minStringLength, maxStringLength, chars);
            b.append(subD);
            if (i < maxSubDomains - 1) {
                b.append(".");
            }
        }
        // path
        for (int i = minPath; i < maxPath; i++) {
            String subD = randomString(minStringLength, maxStringLength, chars);
            b.append(subD);
            if (i < maxPath - 1) {
                b.append("/");
            }
        }
        return b.toString();
    }

    public DynamicMessage randomGPBMessage(Descriptor desc, int maxRepeated, int minString, int maxString, boolean optRepPresent) {
        DynamicMessage.Builder dmb = DynamicMessage.newBuilder(desc);
        for (FieldDescriptor fd : desc.getFields()) {
            setField(dmb, fd, maxRepeated, minString, maxString, optRepPresent);
        }
        return dmb.build();
    }

    public DynamicMessage randomGPBMessage(Descriptor desc, int maxRepeated, int maxString) {
        DynamicMessage.Builder dmb = DynamicMessage.newBuilder(desc);
        for (FieldDescriptor fd : desc.getFields()) {
            setField(dmb, fd, maxRepeated, 0, maxString, false);
        }
        return dmb.build();
    }

    private void setField(DynamicMessage.Builder dmb, FieldDescriptor fd, int maxRepeated, int minString, int maxString, boolean alwaysAdd) {
        boolean add = random.nextBoolean();
        int min = 0;
        if (alwaysAdd) {
            add = true;
            min = 1;
        }
        boolean opt = fd.isOptional();
        boolean rep = fd.isRepeated();
        boolean req = fd.isRequired();
        switch (fd.getJavaType()) {
            case BOOLEAN:
                if (opt && add || req) {
                    Object obj = random.nextBoolean();
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = random.nextBoolean();
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case BYTE_STRING:
                if (opt && add || req) {
                    Object obj = ByteString.copyFromUtf8(randomString(minString, maxString));
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = ByteString.copyFromUtf8(randomString(minString, maxString));
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case DOUBLE:
                if (opt && add || req) {
                    Object obj = random.nextDouble();
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = random.nextDouble();
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case ENUM:
                EnumDescriptor enumType = fd.getEnumType();
                List<EnumValueDescriptor> values = enumType.getValues();
                if (opt && add || req) {
                    Object obj = values.get(random.nextInt(values.size()));
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = values.get(random.nextInt(values.size()));
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case FLOAT:
                if (opt && add || req) {
                    Object obj = random.nextFloat();
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = random.nextFloat();
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case INT:
                if (opt && add || req) {
                    Object obj = random.nextInt();
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = random.nextInt();
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case LONG:
                if (opt && add || req) {
                    Object obj = random.nextLong();
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = random.nextLong();
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case MESSAGE:
                if (opt && add || req) {
                    Object obj = randomGPBMessage(fd.getMessageType(), maxRepeated, maxString);
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = randomGPBMessage(fd.getMessageType(), maxRepeated, maxString);
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            case STRING:
                if (opt && add || req) {
                    Object obj = randomString(minString, maxString);
                    dmb.setField(fd, obj);
                } else if (rep) {
                    int cnt = min + random.nextInt(maxRepeated);
                    for (int i = 0; i < cnt; i++) {
                        Object obj = randomString(minString, maxString);
                        dmb.addRepeatedField(fd, obj);
                    }
                }
                break;
            default:
                throw new IllegalArgumentException("Unknown argument " + fd);
        }
    }

    public String randomStringGUID(int length) {
        return BaseEncoding.base16().lowerCase().encode(randomBytes(length));
    }

    public byte[] randomBytes(int length) {
        byte[] guid = new byte[length];
        random.nextBytes(guid);
        return guid;
    }

    public String[] randomIPv6(int minCnt, int maxCnt) {
        int cnt = minCnt + random.nextInt(maxCnt);
        String[] ar = new String[cnt];
        for (int z = 0; z < cnt; z++) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 7; i++) {
                sb.append(randomHex(4));
                sb.append(":");
            }
            sb.append(randomHex(4));
            ar[z] = sb.toString();
        }
        return ar;
    }

    public String[] randomIPv4(int minCnt, int maxCnt) {
        int cnt = minCnt + random.nextInt(maxCnt);
        String[] ar = new String[cnt];
        for (int z = 0; z < cnt; z++) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 3; i++) {
                sb.append(random.nextInt(255) + "");
                sb.append(".");
            }
            sb.append(random.nextInt(255) + "");
            ar[z] = sb.toString();
        }
        return ar;
    }

    public String randomHex(int length) {
        String s = "abcdef0123456789";
        return randomString(length, s.toCharArray());
    }

    public String randomHex(int minLength, int maxLength) {
        String s = "abcdef0123456789";
        return randomString(minLength, maxLength, s.toCharArray());
    }

    public String randomString(int minLength, int maxLength) {
        int length = minLength + random.nextInt(maxLength + 1);
        return randomString(length);
    }

    public String randomString(int length) {
        String s = "qwertyuiopasdfghjklzxcvbnm1234567890~!@#$%^&*()_+{}|:<>?\"";
        String s2 = "qwertyuiopasdfghjklzxcvbnm".toUpperCase();
        String ss = s + s2;
        return randomString(length, ss.toCharArray());
    }

    public String randomString(int minLength, int maxLength, char[] chars) {
        int length = minLength + random.nextInt(maxLength + 1);
        return randomString(length, chars);
    }

    public String randomString(int length, char[] chars) {
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < length; i++) {
            char c = chars[random.nextInt(chars.length)];
            out.append(c);
        }
        return out.toString();
    }

    public String generateRandomHexString(final int count) {
        if (count == 0) {
            return "";
        } else if (count < 0) {
            throw new IllegalArgumentException("Requested random hex string" + " length " + count
                    + " is less than 0.");
        }

        final char[] buffer = new char[count];
        int offset = 0;
        while (offset < count) {
            long temp = random.nextLong();
            for (int i = 0; i < HEX_DIGITS_PER_LONG && offset < count; i++) {
                buffer[offset] = HEX_DIGITS[(int) (temp) & LAST_HEX_DIGIT_MASK];
                temp >>>= BITS_PER_HALF_BYTE;
                offset += 1;
            }
        }
        return new String(buffer);
    }
}
